﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Reflection;
using System.Diagnostics;
using RichTextEditor;
using System.ServiceModel;
using EditorLauncher;
using System.Threading;
using System.Xml;
using System.Xml.Linq;

namespace RichTextEditor
{
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("7B6BED7E-6C42-438F-9632-4C6E65686697")]
    public interface IRichTextEditor 
    {
        string RichText { get; set; }
        byte[] ImageData { get; set; }

        event OnRichTextHandler OnRichText;
        event OnImageHandler OnImage;
        event OnCloseHandler OnClose;

        void Shutdown();
        void LoadEditor();
        void LaunchEditor(string path);
        string GetText(string richText);
        void Unhook();
        void SetHook(int hwnd);
        void SetImageData([MarshalAsAttribute(UnmanagedType.SafeArray)] ref byte[] imageData);
    } 

    [ProgId("RichTextEditor.Editor")]
    [ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(Events))]
    [Guid("3F709351-AA54-4929-AEC7-88BB265F3B41")]
    [ComVisible(true)]
    [CallbackBehavior(IncludeExceptionDetailInFaults = true)]
    public class ComLoader : IRichTextEditor, ICallback
    {
        private EditorWindow editor;
        private NetTcpBinding binding;
        private EndpointAddress address;
        private IService proxy;
        string richText;
        byte[] imageData;
        private const int GWL_WNDPROC = -4;
        private const int WM_ACTIVATEAPP = 0x1C;
        private bool isHooked;
        private int lpPrevWndProc;
        private int hwndHook;
        private WindowProcHandler callback;

        private delegate int WindowProcHandler(int hwnd, int msg, int wParam, int lParam);
        public delegate void OnAppActivateHandler();

        public event OnRichTextHandler OnRichText;
        public event OnImageHandler OnImage;
        public event OnCloseHandler OnClose;
        public event OnAppActivateHandler OnAppActivate;

        [DllImport("user32.dll", EntryPoint="SetWindowLong")]
        static extern int SetWindowLongProc(int hWnd, int nIndex, WindowProcHandler dwNewLong);
        [DllImport("user32.dll")]
        static extern int SetWindowLong(int hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll")]
        static extern int CallWindowProc(int lpPrevWndFunc, int hwnd, int msg, int wParam, int lParam);

        [CallbackBehavior(IncludeExceptionDetailInFaults=true)]
        private class ServiceClient : DuplexClientBase<IService>
        {
            public ServiceClient(NetTcpBinding binding, EndpointAddress address, ICallback internalCallback) : base(new InstanceContext(internalCallback), binding, address)
            {
            }
        }

        public ComLoader()
        {
            var readerQuotas = new XmlDictionaryReaderQuotas();

            binding = new NetTcpBinding(SecurityMode.Transport);
            address = new EndpointAddress("net.tcp://localhost:8001");

            readerQuotas.MaxStringContentLength = Int32.MaxValue;
            readerQuotas.MaxArrayLength = Int32.MaxValue;
            readerQuotas.MaxBytesPerRead = Int32.MaxValue;
            readerQuotas.MaxDepth = Int32.MaxValue;
            readerQuotas.MaxNameTableCharCount = Int32.MaxValue;

            binding.SendTimeout = TimeSpan.FromMinutes(1);
            binding.OpenTimeout = TimeSpan.FromMinutes(1);
            binding.CloseTimeout = TimeSpan.FromMinutes(1);
            binding.ReceiveTimeout = TimeSpan.FromMinutes(10);
            binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            binding.MaxBufferPoolSize = int.MaxValue;
            binding.MaxReceivedMessageSize = int.MaxValue;
            binding.ReaderQuotas = readerQuotas;
        }

        private int WindowProc(int hwnd, int msg, int wParam, int lParam)
        {
            if (msg == WM_ACTIVATEAPP)
            {
                OnAppActivate();
            }
            else if (msg == 0x281)
            {
                OnAppActivate();
            }

            //Debug.WriteLine(string.Format("{0:x}", msg));

            return CallWindowProc(lpPrevWndProc, hwnd, msg, wParam, lParam);
        }

        public void SetHook(int hwnd)
        {
            if (isHooked)
            {
                Debugger.Break();
            }
            else
            {
                hwndHook = hwnd;

                callback = new WindowProcHandler(WindowProc);
    
                lpPrevWndProc = SetWindowLongProc(hwndHook, GWL_WNDPROC, callback);
                isHooked = true;
            }
        }

        public void Unhook()
        {
            int result;

            result = SetWindowLong(hwndHook, GWL_WNDPROC, lpPrevWndProc);
    
            isHooked = false;
        }

        public void Shutdown()
        {
            try
            {
                var response = proxy.Close();

                ((IClientChannel)proxy).Dispose();

                Thread.Sleep(1000);
            }
            catch
            {
            }

            var process = Process.GetProcesses().Where(p => p.ProcessName == "EditorLauncher").SingleOrDefault();

            if (process != null)
            {
                process.Kill();
            }
        }

        public void CloseEditor()
        {
            if (editor != null)
            {
                Action action = () =>
                {
                    editor.Close();
                };

                editor.Dispatcher.Invoke(action);
            }
        }

        public void LaunchEditor(string path)
        {
            var process = Process.GetProcesses().Where(p => p.ProcessName == "EditorLauncher").SingleOrDefault();
            var response = string.Empty;

            if (process == null)
            {
                var assembly = Assembly.GetExecutingAssembly();
                var pathRoot = assembly.ManifestModule.FullyQualifiedName;
                var exe = new FileInfo(path);

                Debug.Assert(exe.Exists);

                process = Process.Start(exe.FullName);

                Thread.Sleep(1000);
            }

            if (proxy == null)
            {
                var serviceClient = new ServiceClient(binding, address, this);

                proxy = (IService) serviceClient.InnerDuplexChannel;
                response = proxy.Connect();
            }

            response = proxy.LoadEditorWithData(imageData, richText);
        }

        public void LoadEditor()
        {
            Debug.Assert(editor == null);

            editor = new EditorWindow();

            editor.RichText = this.RichText;
            editor.ImageData = this.ImageData;

            editor.OnRichText += (t) =>
            {
                if (OnRichText != null)
                {
                    OnRichText(t);
                }
            };

            editor.OnImage += (ref byte[] i) =>
            {
                if (OnImage != null)
                {
                    OnImage(ref i);
                }
            };

            var result = editor.ShowDialog();

            if (OnClose != null)
            {
                OnClose();
            }

            editor = null;
        }

        void ICallback.OnRichText(string richText)
        {
            OnRichText(richText);
        }

        void ICallback.OnImage(byte[] imageData)
        {
            OnImage(ref imageData);
        }

        void ICallback.OnClose()
        {
            OnClose();
        }

        public string RichText
        {
            [return: MarshalAsAttribute(UnmanagedType.BStr)]
            get
            {
                return richText;
            }
            [param: MarshalAsAttribute(UnmanagedType.BStr)]
            set
            {
                richText = value;
            }
        }

        public byte[] ImageData
        {
            [return: MarshalAsAttribute(UnmanagedType.SafeArray)]
            get
            {
                return imageData;
            }
            [param: MarshalAsAttribute(UnmanagedType.SafeArray)]
            set
            {
                imageData = value;
            }
        }

        public void SetImageData(ref byte[] imageData)
        {
            if (imageData.Length < 2)
            {
                this.imageData = null;
            }
            else
            {
                this.imageData = imageData;
            }
        }

        public string GetText(string richText)
        {
            var document = XDocument.Parse(richText);
            var text = string.Empty;
            var fragments = document.Descendants().Where(e => e.Name.LocalName == "Run").Select(e => e.Value).ToArray();

            text = string.Join(", ", fragments);

            return text;
        }
    }

    public delegate void OnRichTextHandler([MarshalAsAttribute(UnmanagedType.BStr)] string richText);
    public delegate void OnImageHandler([MarshalAsAttribute(UnmanagedType.SafeArray)] ref byte[] imageData);
    public delegate void OnCloseHandler();

    /// <summary>
    /// This interface shows events to javascript
    /// </summary>
    [Guid("76279926-CCCE-4922-869E-9D369271877B")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface Events
    {
        [DispId(0x60020001)]
        void OnRichText([MarshalAsAttribute(UnmanagedType.BStr)] string richText);
        [DispId(0x60020002)]
        void OnImage([MarshalAsAttribute(UnmanagedType.SafeArray)] ref byte[] imageData);
        [DispId(0x60020003)]
        void OnAppActivate();
        [DispId(0x60020004)]
        void OnClose();
    }
}
